`include "data_width.vh"

module get_edge_off #(parameter
    MEM_DWIDTH          = `MEM_DWIDTH,
    DST_ID_DWIDTH       = `DST_ID_DWIDTH,
    EDGE_OFF_DWIDTH     = `EDGE_OFF_DWIDTH,
    VERTEX_PIPE_NUM     = `VERTEX_PIPE_NUM,
    MEM_AWIDTH          = `MEM_AWIDTH,
    VERTEX_MASK_WIDTH   = `VERTEX_MASK_WIDTH
    ) (
        input                                                   clk,
        input                                                   front_rst,
        input [MEM_DWIDTH * 2 - 1 : 0]                              mem_data,
        input                                                   mem_data_valid,
        input [DST_ID_DWIDTH * VERTEX_PIPE_NUM - 1 : 0]         front_dst_id,
        input [VERTEX_PIPE_NUM - 1 : 0]                         front_dst_data_valid,
        input                                                   back_stage_vertex_full,

        // para in
        input [DST_ID_DWIDTH - 1 : 0]                           front_dst_id_ed,
        input [EDGE_OFF_DWIDTH - 1 : 0]                         front_edge_off_ed,
        input [MEM_AWIDTH - 1 : 0]                              front_edge_info_addr_ed,
        input [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]     front_mem_edge_ed,
        input                                                   front_para_valid,

        // para out
        output                                                  rst,
        output [EDGE_OFF_DWIDTH - 1 : 0]                        edge_off_ed,
        output [MEM_AWIDTH - 1 : 0]                             edge_info_addr_ed,
        output [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]    mem_edge_ed,
        output                                                  para_valid,

        output                                                  rd_plus_valid,
        output                                                  buffer_full_vertex,
        output [EDGE_OFF_DWIDTH * (VERTEX_PIPE_NUM + 1) - 1 : 0] edge_off,
        output [DST_ID_DWIDTH * VERTEX_PIPE_NUM - 1 : 0]        dst_id,
        output [VERTEX_PIPE_NUM - 1 : 0]                        dst_data_valid);

    wire any_front_dst_data_valid;
    wire mem_buffer_empty, mem_buffer_full;
    wire [VERTEX_PIPE_NUM - 1 : 0] dst_buffer_empty, dst_buffer_full;
    wire read_next_valid;
    // 点流水线为 16 条，边流水线为 32 条
    wire [DST_ID_DWIDTH * VERTEX_PIPE_NUM - 1 : 0] top_dst_id;

    assign any_front_dst_data_valid = front_dst_data_valid[0];
    assign buffer_full_vertex = dst_buffer_full[0];

    get_edge_off_para_trans P (
        .clk                        (clk),
        .front_rst                  (front_rst),
        .front_edge_off_ed          (front_edge_off_ed),
        .front_edge_info_addr_ed    (front_edge_info_addr_ed),
        .front_mem_edge_ed          (front_mem_edge_ed),
        .front_para_valid           (front_para_valid),

        .rst                        (rst),
        .edge_off_ed                (edge_off_ed),
        .edge_info_addr_ed          (edge_info_addr_ed),
        .mem_edge_ed                (mem_edge_ed),
        .para_valid                 (para_valid));

    get_edge_off_mem_rev E0 (
        .clk(clk), .rst(front_rst),
        .front_dst_id_ed(front_dst_id_ed),
        .mem_data(mem_data), .mem_data_valid(mem_data_valid),
        .read_next_valid(read_next_valid),
        .top_dst_id_final(top_dst_id[DST_ID_DWIDTH * VERTEX_PIPE_NUM - 1 : DST_ID_DWIDTH * (VERTEX_PIPE_NUM - 1)]),

        .rd_plus_valid(rd_plus_valid),
        .buffer_empty(mem_buffer_empty), .buffer_full(mem_buffer_full),
        .edge_off(edge_off));
    
    generate
        genvar i;
        for (i = 0; i < VERTEX_PIPE_NUM; i = i + 1) begin : M3_BLOCK_1
            get_edge_off_vertex_single V (
                .clk(clk), .rst(front_rst),
                .front_dst_id(front_dst_id[(i + 1) * DST_ID_DWIDTH - 1 : i * DST_ID_DWIDTH]), .front_dst_data_valid(front_dst_data_valid[i]),
                .any_front_dst_data_valid(any_front_dst_data_valid), .read_next_valid(read_next_valid),

                .buffer_empty(dst_buffer_empty[i]), .buffer_full(dst_buffer_full[i]),
                .top_dst_id(top_dst_id[(i + 1) * DST_ID_DWIDTH - 1 : i * DST_ID_DWIDTH]),
                .dst_id(dst_id[(i + 1) * DST_ID_DWIDTH - 1 : i * DST_ID_DWIDTH]), .dst_data_valid(dst_data_valid[i]));
        end
    endgenerate

    get_edge_off_control C0 (
        .mem_buffer_empty(mem_buffer_empty), .dst_buffer_empty(dst_buffer_empty[0]), .back_stage_vertex_full(back_stage_vertex_full),

        .read_next_valid(read_next_valid));

endmodule

module get_edge_off_para_trans #(parameter
    EDGE_OFF_DWIDTH     = `EDGE_OFF_DWIDTH,
    MEM_AWIDTH          = `MEM_AWIDTH,
    VERTEX_MASK_WIDTH   = `VERTEX_MASK_WIDTH
    ) (
        input                                                       clk,
        input                                                       front_rst,
        input [EDGE_OFF_DWIDTH - 1 : 0]                             front_edge_off_ed,
        input [MEM_AWIDTH - 1 : 0]                                  front_edge_info_addr_ed,
        input [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]         front_mem_edge_ed,
        input                                                       front_para_valid,

        output reg                                                  rst,
        output reg [EDGE_OFF_DWIDTH - 1 : 0]                        edge_off_ed,
        output reg [MEM_AWIDTH - 1 : 0]                             edge_info_addr_ed,
        output reg [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]    mem_edge_ed,
        output reg                                                  para_valid);

    always @ (posedge clk) begin
        if (rst) begin
            edge_info_addr_ed   <= 0;
            edge_off_ed         <= 0;
            mem_edge_ed         <= 0;
            para_valid          <= 1'b0;
        end
        else begin
            if (front_para_valid) begin
                edge_info_addr_ed   <= front_edge_info_addr_ed;
                edge_off_ed         <= front_edge_off_ed;
                mem_edge_ed         <= front_mem_edge_ed;
                para_valid          <= 1'b1;
            end
        end
    end

    always @ (posedge clk) begin
        rst <= front_rst;
    end

endmodule

module get_edge_off_mem_rev #(parameter
    MEM_DWIDTH = `MEM_DWIDTH, DST_ID_DWIDTH = `DST_ID_DWIDTH, MEM_PER_DWIDTH = `MEM_PER_DWIDTH,
    PIPE_BUFFER_SIZE = `PIPE_BUFFER_SIZE, PIPE_AM_LEVEL = `PIPE_AM_LEVEL, PIPE_BUFFER_PTR_WIDTH = `PIPE_BUFFER_PTR_WIDTH,
    EDGE_OFF_DWIDTH = `EDGE_OFF_DWIDTH, VERTEX_PIPE_NUM = `VERTEX_PIPE_NUM
    ) (
        input clk,
        input rst,
        input [DST_ID_DWIDTH - 1 : 0] front_dst_id_ed,
        input [MEM_DWIDTH * 2 - 1 : 0] mem_data,
        input mem_data_valid,
        input read_next_valid,
        input [DST_ID_DWIDTH - 1 : 0] top_dst_id_final,

        output reg rd_plus_valid,
        output buffer_empty,
        output buffer_full,
        output reg [EDGE_OFF_DWIDTH * (VERTEX_PIPE_NUM + 1) - 1 : 0] edge_off);

    reg [MEM_DWIDTH - 1 : 0] mem_data_buffer_1 [0 : PIPE_BUFFER_SIZE - 1];  // high
    reg [MEM_DWIDTH - 1 : 0] mem_data_buffer_2 [0 : PIPE_BUFFER_SIZE - 1];  // low
    reg [PIPE_BUFFER_PTR_WIDTH - 1 : 0] write_ptr, read_ptr, read_ptr_1;
    wire [PIPE_BUFFER_PTR_WIDTH - 1 : 0] gap_ptr;

    // buffer input 
    always @ (posedge clk) begin
        if (rst) begin
            write_ptr <= 0;
        end
        else begin
            if (mem_data_valid) begin
                // {mem_data_buffer_1[write_ptr], mem_data_buffer_2[write_ptr]} <= mem_data;
                mem_data_buffer_1[write_ptr] <= mem_data[MEM_DWIDTH * 2 - 1 : MEM_DWIDTH];
                mem_data_buffer_2[write_ptr] <= mem_data[MEM_DWIDTH - 1 : 0];

                write_ptr <= write_ptr + 1;
            end
        end
    end

    assign gap_ptr = (write_ptr - read_ptr);
    assign buffer_empty = (gap_ptr < 2) || rst;
    assign buffer_full = (gap_ptr >= PIPE_AM_LEVEL) || rst;

    // buffer output
    always @ (posedge clk) begin
        if (rst) begin
            rd_plus_valid <= 1'b0;
            edge_off <= 0;

            read_ptr <= 0;
            read_ptr_1 <= 1;
        end
        else begin
            if (read_next_valid) begin
                case (top_dst_id_final[4])
                    1'b0 : begin
                        edge_off[EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM - 1 : 0] <= mem_data_buffer_2[read_ptr][EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM - 1 : 0];
                        edge_off[EDGE_OFF_DWIDTH * (VERTEX_PIPE_NUM + 1) - 1 : EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM] <= mem_data_buffer_1[read_ptr][EDGE_OFF_DWIDTH - 1 : 0];
                    end
                    1'b1 : begin
                        edge_off[EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM - 1 : 0] <= mem_data_buffer_1[read_ptr][EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM - 1 : 0];
                        edge_off[EDGE_OFF_DWIDTH * (VERTEX_PIPE_NUM + 1) - 1 : EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM] <= mem_data_buffer_2[read_ptr_1][EDGE_OFF_DWIDTH - 1 : 0];
                    end
                endcase
                if ((top_dst_id_final[4 : 0] == 31) || (top_dst_id_final == front_dst_id_ed + 15)) begin
                    rd_plus_valid <= 1'b1;

                    read_ptr <= read_ptr + 1;
                    read_ptr_1 <= read_ptr_1 + 1;
                end
                else begin
                    rd_plus_valid <= 1'b0;
                end
            end
            else begin
                rd_plus_valid <= 1'b0;
                edge_off <= 0;
            end
        end
    end

endmodule

module get_edge_off_vertex_single #(parameter
    DST_ID_DWIDTH = `DST_ID_DWIDTH,
    PIPE_BUFFER_SIZE = `PIPE_BUFFER_SIZE, PIPE_AM_LEVEL = `PIPE_AM_LEVEL, PIPE_BUFFER_PTR_WIDTH = `PIPE_BUFFER_PTR_WIDTH
    ) (
        input clk,
        input rst,
        input [DST_ID_DWIDTH - 1 : 0] front_dst_id,
        input front_dst_data_valid,
        input any_front_dst_data_valid,
        input read_next_valid,

        output buffer_empty,
        output buffer_full,
        output [DST_ID_DWIDTH - 1 : 0] top_dst_id,
        output reg [DST_ID_DWIDTH - 1 : 0] dst_id,
        output reg dst_data_valid);
    
    reg [DST_ID_DWIDTH - 1 : 0] dst_id_buffer [0 : PIPE_BUFFER_SIZE - 1];
    reg valid_buffer [0 : PIPE_BUFFER_SIZE - 1];
    reg [PIPE_BUFFER_PTR_WIDTH - 1 : 0] write_ptr, read_ptr;
    wire [PIPE_BUFFER_PTR_WIDTH - 1 : 0] gap_ptr;
    wire top_dst_data_valid;

    dst_id_fifo_fall_through idf (
        .clk(clk), .srst(rst),
        .din(front_dst_id), .wr_en(any_front_dst_data_valid), .rd_en(read_next_valid),

        .dout(top_dst_id),
        .empty(buffer_empty), .prog_full(buffer_full));
    
    valid_fifo_fall_through vf (
        .clk(clk), .srst(rst),
        .din(front_dst_data_valid), .wr_en(any_front_dst_data_valid), .rd_en(read_next_valid),

        .dout(top_dst_data_valid));

    // output
    always @ (posedge clk) begin
        if (rst) begin
            dst_id <= 0;
            dst_data_valid <= 1'b0;
        end
        else begin
            if (read_next_valid) begin
                dst_id <= top_dst_id;
                dst_data_valid <= top_dst_data_valid;
            end
            else begin
                dst_id <= 0;
                dst_data_valid <= 1'b0;
            end
        end
    end

endmodule

module get_edge_off_control (
    input mem_buffer_empty,
    input dst_buffer_empty,
    input back_stage_vertex_full,

    output read_next_valid);

    assign read_next_valid = !(mem_buffer_empty || dst_buffer_empty || back_stage_vertex_full);

endmodule